package gorm

import (
	"github.com/sirupsen/logrus"
	"github.com/ingopansa/userinfoservice/storage"
	"github.com/ingopansa/userinfoservice/model"
	"github.com/jinzhu/gorm"
	_ "github.com/jinzhu/gorm/dialects/postgres"

	"errors"
	"fmt"
)

// keysRowID is the ID of the only row we expect to populate the "keys" table.
const keysRowID = "keys"


type GormConfig struct {
	Database string
	User     string
	Password string
	Host     string
	SSL		 GormSSL
}

type GormSSL struct {
	Mode   string
	CAFile string
	// Files for client auth.
	KeyFile  string
	CertFile string
}


type gormConnection struct {
	db	*gorm.DB
	logger logrus.FieldLogger
}



func (p *GormConfig) Open(logger logrus.FieldLogger) (storage.Storage, error) {
	database, err := gorm.Open("postgres", "postgres://"+p.User+"@"+p.Host+"/"+p.Database+"?sslmode="+p.SSL.Mode)
	if err != nil {
		logger.Errorf( "cannot open connection: error=%v", err)
		return nil, err
	}

	database.AutoMigrate(
		&model.User{},
		&model.AccessTokenData{},
		&model.Subject{},
		&model.Group{},
		&model.Key{},
	)

	return &gormConnection{
		db: database,
		logger: logger,
	}, nil
}

func (conn *gormConnection) Close() error {
	return conn.db.Close()
}

func (conn *gormConnection ) GetKeys() (keys storage.Keys, err error){
	conn.logger.Debugf( "GetKeys()" )

	if err := conn.db.Debug().Find(&keys, "id = ?", keysRowID).Error; err != nil {
		conn.logger.Errorf( "cannot get keys: error=%v", err )
		return keys, err
	}

	return keys, nil
}

func (conn *gormConnection ) UpdateKeys(updater func(old storage.Keys) (storage.Keys, error)) error {
	conn.logger.Debugf( "UpdateKeys()" )

	return nil
}

func (conn *gormConnection ) GetUser(sub string)(model.User, error){
	conn.logger.Debugf( "GetUser(): sub=%s", sub )

	errorMessages := map[string]string {
		"subjectNotFound":"subject not found",
		"userNotFound": "user not found",
		"groupsLoadingError":"error loading groups",
	}


	var subject model.Subject
	var user 	model.User
	var groups 	[]model.Group

	if err := conn.db.Debug().Find( &subject, "subject = ?", sub ).Error; err != nil {
		conn.logger.Errorf( fmt.Sprintf("%s: error=%v\n", errorMessages["subjectNotFound"], err) )
		return model.User{}, errors.New(errorMessages["subjectNotFound"] )
	}

	if err:= conn.db.Debug().Find( &user, "id = ?", subject.UserID ).Error; err != nil {
		conn.logger.Errorf( fmt.Sprintf("%s: error=%v\n", errorMessages["userNotFound"], err) )
		return model.User{}, errors.New(errorMessages["userNotFound"])
	}

	if err := conn.db.Debug().Find( &groups, "user_id = ? ", subject.UserID ).Error; err != nil {
		conn.logger.Errorf( fmt.Sprintf("%s: error=%v\n", errorMessages["groupsLoadingError"], err) )
		return model.User{}, errors.New(errorMessages["groupsLoadingError"])
	}

	user.Groups = groups

	return user, nil
}

func (conn *gormConnection ) AddUser(user model.User) error {
	errorMessages := map[string]string {
		"subjectAlreadyExists":"user not added, as subject already exists",
		"cannotReadRecord": "cannot read record",
		"cannotInsertRecord":"cannot insert record",
	}

	conn.logger.Debugf( "gorm.AddUser(): user=%v\n", user )
	//user.Subjects = append(user.Subjects, model.Subject{Subject: user.Subject})
	subjects := []model.Subject{{}}

	if err := conn.db.Debug().Find(&subjects, "Subject = ?", user.Subjects[0].Subject ).Error; err != nil {
		conn.logger.Errorf( fmt.Sprintf("%s: error=%v\n", errorMessages["cannotReadRecord"], err))
		return errors.New(errorMessages["cannotReadRecord"] )
	}

	if !(len(subjects) == 0) {
		conn.logger.Errorf( errorMessages["subjectAlreadyExists"] )
		return errors.New( errorMessages["subjectAlreadyExists"] )
	}

	if err:= conn.db.Debug().Create(&user).Error; err != nil {
		conn.logger.Errorf( fmt.Sprintf("%s: error=%v\n", errorMessages["cannotInsertRecord"], err))
		return errors.New( errorMessages["cannotInsertRecord"] )
	}

	// all good
	return nil
}

func (conn *gormConnection ) ListUsers()([]model.User, error) {
	var users []model.User

	err := conn.db.Debug().Preload("Subjects").Preload("Groups").Find(&users).Error

	return users, err
}